﻿using System;
using System.Collections.Generic;
using System.Text;

namespace daSyntaxAnalyser.Analysers.DfmAnalyser
{
    using daBase.Analyser;
    using daBase.Objects;
    using daBase.Objects.Safe;

    /// <summary>
    /// Лексический анализатор dfm файлов
    /// </summary>
    [AnalyserDefaultSettings("Лексический анализатор dfm файлов", "Неизвестная последовательность языка!", 100, "Анализатор не в состоянии распознать данную символьную последовательность. Возможно она некорректна или анализатор работает неправильно. Сообщите о данной ошибке разработчику.")]
    public class DfmLexicalAnalyser : LayerAnalyser
    {
        // шаблон "одиночка"
        private static DfmLexicalAnalyser instance;

        /// <summary>
        /// Единственный экземпляр класса анализатора
        /// </summary>
        public static DfmLexicalAnalyser Instance
        {
            get
            {
                if (instance == null)
                    instance = new DfmLexicalAnalyser();
                return instance;
            }
        }

        // Текуший анализ
        private string source, currentId;
        private int index;
        private DfmTokens token;
        private FileMap filemap;
        private List<DfmTokenInfo> tokens;

        // ключевые слова
        private Dictionary<string, DfmTokens> keysymbols, keywords;

        /// <summary>
        /// Инициализация ключевых слов
        /// </summary>
        private void InitKeyWords()
        {
            if (keywords == null)
            {
                keywords = new Dictionary<string, DfmTokens>();
                keywords.Add("object", DfmTokens.Object);
                keywords.Add("inherited", DfmTokens.Inherited);
                keywords.Add("inline", DfmTokens.Inline);
                keywords.Add("item", DfmTokens.Item);
                keywords.Add("end", DfmTokens.End);
            }
        }

        /// <summary>
        /// Инициализация ключевых неслов
        /// </summary>
        private void InitKeySymbols()
        {
            if (keysymbols == null)
            {
                keysymbols = new Dictionary<string, DfmTokens>();
                keysymbols.Add(".", DfmTokens.Dot);
                keysymbols.Add("(", DfmTokens.LBr);
                keysymbols.Add(")", DfmTokens.RBr);
                keysymbols.Add("[", DfmTokens.LSBr);
                keysymbols.Add("]", DfmTokens.RSBr);
                keysymbols.Add("<", DfmTokens.LTBr);
                keysymbols.Add(">", DfmTokens.RTBr);
                keysymbols.Add("{", DfmTokens.LFBr);
                keysymbols.Add("}", DfmTokens.RFBr);
                keysymbols.Add("=", DfmTokens.Assigment);
                keysymbols.Add("+", DfmTokens.Addition);
                keysymbols.Add("-", DfmTokens.Subtruction);
                keysymbols.Add(":", DfmTokens.Colon);
                keysymbols.Add(",", DfmTokens.Comma);
            }
        }
        
        /// <summary>
        /// Инициализация карты файла
        /// </summary>
        private void InitFileMap()
        {
            if (filemap == null)
                filemap = new FileMap();
            else filemap.Reset();
        }

        /// <summary>
        /// Инициализация
        /// </summary>
        private void Initialize()
        {
            InitKeyWords();
            InitKeySymbols();
            InitFileMap();
            index = 0;
            token = DfmTokens.Undefined;
            currentId = string.Empty;
            tokens = new List<DfmTokenInfo>();
        }

        /// <summary>
        /// Инициализация
        /// </summary>
        /// <param name="FileName">имя анализируемого файла</param>
        /// <param name="Source">исходный код файла</param>
        private void Initialize(string FileName, string Source)
        {
            Initialize();
            file = FileName;
            source = Source;
        }

        /// <summary>
        /// Считывание очередного токена
        /// </summary>
        private int Scan()
        {
            int start = index;
            token = DfmTokens.Undefined;
            currentId = string.Empty;
            if (index >= source.Length)
            {
                token = DfmTokens.FileEnd;
                return start;
            }
            char c;
            int state = 1;
            while (index < source.Length)
            {
                c = source[index];
                switch (state)
                {
                    case 1:
                        if (DChar.IsLetter(c) || (c == '_'))
                        {
                            currentId += c;
                            token = DfmTokens.Id;
                            state = 2;
                        }
                        else if (DChar.IsHexSymbol(c))
                        {
                            currentId += c;
                            token = DfmTokens.Digits;
                            state = 3;
                        }
                        else
                        {
                            switch (c)
                            {
                                case '$':
                                    state = 4;
                                    break;
                                case '#':
                                    state = 6;
                                    break;
                                case '\'':
                                    state = 8;
                                    break;
                                case '\n':
                                    start++;
                                    filemap.AddLine(index + 1);
                                    break;
                                case '.':
                                case '(':
                                case ')':
                                case '[':
                                case ']':
                                case '<':
                                case '>':
                                case '{':
                                case '}':
                                case '=':
                                case '+':
                                case '-':
                                case ':':
                                case ',':
                                    index++;
                                    token = keysymbols[c.ToString()];
                                    return start;
                                default:
                                    start++;
                                    break;
                            }
                        }
                        break;
                    case 2:
                        if (DChar.IsLetterOrDigit(c) || (c == '_')) currentId += c;
                        else return start;
                        break;
                    case 3:
                        if (DChar.IsHexSymbol(c)) currentId += c;
                        else return start;
                        break;
                    case 4:
                        if (DChar.IsHexSymbol(c))
                        {
                            currentId += c;
                            token = DfmTokens.HexConstant;
                            state = 5;
                        }
                        break;
                    case 5:
                        if (DChar.IsHexSymbol(c)) currentId += c;
                        else return start;
                        break;
                    case 6:
                        if (DChar.IsDigit(c))
                        {
                            currentId += c;
                            token = DfmTokens.Char;
                            state = 7;
                        }
                        else if (c == '$')
                        {
                            currentId += c;
                            state = 9;
                        }
                        else return start;
                        break;
                    case 7:
                        if (DChar.IsDigit(c)) currentId += c;
                        else return start;
                        break;
                    case 8:
                        if (c == '\'')
                        {
                            token = (currentId.Length == 1) ? DfmTokens.Char : DfmTokens.String;
                            index++;
                            return start;
                        }
                        else if (c == '\n') return start;
                        else currentId += c;
                        break;
                    case 9:
                        if (DChar.IsHexSymbol(c))
                        {
                            currentId += c;
                            token = DfmTokens.Char;
                            state = 10;
                        }
                        else return start;
                        break;
                    case 10:
                        if (DChar.IsHexSymbol(c)) currentId += c;
                        else return start;
                        break;
                }
                index++;
            }
            return start;
        }

        /// <summary>
        /// Считывание очередного токена выделением ключевых слов
        /// </summary>
        private DfmTokenInfo FullScan()
        {
            int line = filemap.Line(Scan());
            string lowerid = currentId.ToLower();
            if ((token == DfmTokens.Id) && keywords.ContainsKey(lowerid))
                token = keywords[lowerid];
            return new DfmTokenInfo(token, currentId, line);
        }

        /// <summary>
        /// Конструктор
        /// </summary>
        public DfmLexicalAnalyser()
        {
            Initialize();
        }

        /// <summary>
        /// Выделение строки кода, содержащей заданную позицию
        /// </summary>
        /// <param name="Source">исходный код</param>
        /// <param name="Index">позиция</param>
        /// <param name="Map">карта исходного кода</param>
        /// <returns></returns>
        private string ExtractLine()
        {
            int Line = filemap.Line(index),
                Start = filemap.Index(Line),
                Length = filemap.Index(Line + 1) - Start;
            return source.Substring(Start, Length);
        }

        /// <summary>
        /// Трансляция dfm-файла
        /// </summary>
        /// <param name="FileName">имя анализируемого dfm-файла</param>
        /// <param name="Source">исходный код файла</param>
        /// <param name="MetaTags">мета-теги соответствующего pas/dpr-файла</param>
        /// <returns>список токенов dfm-файла</returns>
        public List<DfmTokenInfo> Translate(string FileName, string Source, SafeDictionary<string, string> MetaTags)
        {
            try
            {
                Initialize(FileName, Source);
                tokens.Add(new DfmTokenInfo(DfmTokens.Undefined, string.Empty, 0));
                while (token != DfmTokens.FileEnd)
                    tokens.Add(FullScan());
                return tokens;
            }
            catch (Exception e)
            {
                if (e is DanalyserError)
                    AddError(filemap.Line(index), ExtractLine(), MetaTags);
                else AddUndefinedError(file, tokens[tokens.Count - 1].Line, tokens[tokens.Count - 1].Text, new SafeDictionary<string, string>(s => string.Empty));
            }
            return tokens;
        }
    }
}
